16 设计模式——中介者模式

返回设计模式博客目录

介绍


中介者(Mediator)模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂。

如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参力口工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。

在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。

优点

  • 降低了对象之间的耦合性,使得对象易于独立地被复用。
  • 将对象间的多对多关联转变为一对多的关联,提高系统的灵活性,使得系统易于维护和扩展。

缺点

  • 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

使用场景

当程序存在大量的类时,多个对象之间存在着依赖的关系,呈现出网状结构,那么程序的可读性和可维护性就变差了,并且修改一个类需要牵涉到其他类,不符合开闭原则。

网状结构

因此我们可以引入中介者,将网状结构转化成星型结构,可以降低程序的复杂性,并且可以减少各个对象之间的耦合。

星型结构

结构与实现


模式包含以下主要角色。

  • Mediator(抽象中介者角色):抽象类或者接口,定义统一的接口,用于各同事角色之间的通信。
  • ConcreteMediator(具体中介者角色):继承或者实现了抽象中介者,实现了父类定义的方法,协调各个具体同事进行通信。
  • Colleague(抽象同事角色):抽象类或者接口,定义统一的接口,它只知道中介者而不知道其他同事对象。
  • ConcreteColleague(具体同事角色):继承或者实现了抽象同事角色,每个具体同事类都知道自己本身的行为,其他的行为只能通过中介者去进行。

其结构图如下图所示。

中介者模式的实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class MediatorPattern {
public static void main(String[] args) {
Mediator md = new ConcreteMediator();
Colleague c1,c2;
c1 = new ConcreteColleague1();
c2 = new ConcreteColleague2();
md.register(c1);
md.register(c2);
c1.send();
System.out.println("-------------");
c2.send();
}
}
// 抽象中介者
abstract class Mediator {
public abstract void register(Colleague colleague);
public abstract void relay(Colleague cl); // 转发
}
// 具体中介者
class ConcreteMediator extends Mediator {
private List<Colleague> colleagues = new ArrayList<>();
public void register(Colleague colleague) {
if (!colleagues.contains(colleague)) {
colleagues.add(colleague);
colleague.setMedium(this);
}
}
public void relay(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)) {
ob.receive();
}
}
}
}
// 抽象同事类
abstract class Colleague {
protected Mediator mediator;
public void setMedium(Mediator mediator) {
this.mediator = mediator;
}
public abstract void receive();
public abstract void send();
}
// 具体同事类
class ConcreteColleague1 extends Colleague {
public void receive() {
System.out.println("具体同事类1收到请求。");
}
public void send() {
System.out.println("具体同事类1发出请求。");
mediator.relay(this); // 请中介者转发
}
}
// 具体同事类
class ConcreteColleague2 extends Colleague {
public void receive() {
System.out.println("具体同事类2收到请求。");
}
public void send() {
System.out.println("具体同事类2发出请求。");
mediator.relay(this); //请中介者转发
}
}

程序的运行结果如下:

1
2
3
4
5
具体同事类1发出请求。
具体同事类2收到请求。
-------------
具体同事类2发出请求。
具体同事类1收到请求。

示例


我们的电脑组件异常之多,读取一个 CD 光盘需要使用 CPU 、光驱、声卡、显卡、内存等一系列组件。如果我们不使用设计模式,那我们每个组件都要保持其他组件的引用,这就会造成我们的类结构复杂难懂,变成一个网状结构。

解决方法:使用中介者模式可以使一系列组件只和我们的中介(主板)打交道,这就将网状结构变成星型结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
public abstract class Mediator {
/**
* 同事对象改变时通知中介者的方法
* 同事对象改变时由中介者去通知其他同事。
* @param c Colleague
*/
public abstract void changed(Colleague c);
}
//主板
public class MainBoard extends Mediator {
private CDDevice cd; // 光驱设备
private CPU cpu; // CPU
private SoundCard soundCard; // 声卡设备
private GraphicsCard graphicsCard; // 显卡设备
@Override
public void changed(Colleague c) {
// 如果 CD 读取到数据
if (c == cd) {
handleCD((CDDevice) c);
} else if (c == cpu) {
handleCPU((CPU) c);
}
}
private void handleCD(CDDevice c) {
cpu.setData(c.read());
}
private void handleCPU(CPU c) {
soundCard.videoPlay(c.getDataSound());
graphicsCard.videoPlay(c.getDataVideo());
}
public void setCpu(CPU cpu) {
this.cpu = cpu;
}
public void setSoundCard(SoundCard soundCard) {
this.soundCard = soundCard;
}
public void setgCard(GraphicsCard graphicsCard) {
this.graphicsCard = graphicsCard;
}
public void setCd(CDDevice cd) {
this.cd = cd;
}
}
public abstract class Colleague {
private Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
}
public class CDDevice extends Colleague {
private String data;
public CDDevice(Mediator mediator) {
super(mediator);
}
public String read() {
return data;
}
/**
* 加载视频数据
* @return
*/
public void load() {
data = "视频数据,音频数据";
mediator.changed(this);
}
}
public class CPU extends Colleague {
private String dataVideo, dataSound; // 视频和音频数据
public CPU(Mediator mediator) {
super(mediator);
}
public String getDataVideo() {
return dataVideo;
}
public String getDataSound() {
return dataSound;
}
public void setData(String data) {
// 分割音、视频数据
String[] tmp = data.split(",");
this.dataVideo = tmp[0];
this.dataSound = tmp[1];
// 告诉中介者自身状态改变
mediator.changed(this);
}
}
public class GraphicsCard extends Colleague {
public GraphicsCard(Mediator mediator) {
super(mediator);
}
public void videoPlay(String data){
System.out.println("视频:"+ data);
}
}
public class SoundCard extends Colleague {
public SoundCard(Mediator mediator) {
super(mediator);
}
public void videoPlay(String data){
System.out.println("音频:"+ data);
}
}

每个对象只需要关心自己的职责,自己事情处理完成后通知中介者,让中介者调节指导下一步操作。

客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {
public static void main(String[] args) {
MainBoard mainBoard = new MainBoard();
CDDevice cd = new CDDevice(mainBoard);
CPU cpu = new CPU(mainBoard);
GCard gc = new GCard(mainBoard);
SoundCard sc = new SoundCard(mainBoard);
mainBoard.setCd(cd);
mainBoard.setCpu(cpu);
mainBoard.setgCard(gc);
mainBoard.setSoundCard(sc);
cd.load();
}
}

ANDROID 源码中的实现


ANDROID 中的锁屏功能就用到了中介者模式,KeyguardService(锁屏服务)通过 KeyguardViewMediator(锁屏中介者)来协调各种 Manager 的状态以达到锁屏的功能。这里各种 Manager 都充当了同事的角色。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 锁屏服务,同事角色
public class KeyguardService extends Service {
// 锁屏中介者
private KeyguardViewMediator mKeyguardViewMediator;
@Override
public void onCreate() {
((SystemUIApplication) getApplication()).startServicesIfNeeded();
// 初始化中介者
mKeyguardViewMediator = ((SystemUIApplication) getApplication()).getComponent(KeyguardViewMediator.class);
}
private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
@Override // Binder interface
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
checkPermission();
// 调用中介者的接口
mKeyguardViewMediator.addStateMonitorCallback(callback);
}
@Override // Binder interface
public void verifyUnlock(IKeyguardExitCallback callback) {
checkPermission();
// 调用中介者的接口
mKeyguardViewMediator.verifyUnlock(callback);
}
// 其他代码略
};
}
// 锁屏中介者
public class KeyguardViewMediator extends SystemUI {
// 各种 Manager,同事角色
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
private PowerManager mPM;
private IWindowManager mWM;
private TrustManager mTrustManager;
private SearchManager mSearchManager;
private PowerManager.WakeLock mShowKeyguardWakeLock;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
//其他代码略
// 通过 AudioManager 去播放声音
private void playSound(int soundId) {
if (soundId == 0) return;
final ContentResolver cr = mContext.getContentResolver();
if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) {
mLockSounds.stop(mLockSoundStreamId);
// Init mAudioManager
if (mAudioManager == null) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) return;
mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
}
// If the stream is muted, don't play the sound
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
mLockSoundStreamId = mLockSounds.play(soundId,
mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
}
}
}

KeyguardViewMediator 中通过 playSound 方法能够协调 AudioManager 去控制声音的播放等等,其他 Manager 同理。

实战


登录界面运用中介者模式,如下图所示。

一个简单的登录界面,包含两个文本框、两个复选框、两个按钮。只有 6 个元素,但要实现如下逻辑:
1)当用户名没有输入时,除了账户文本输入框、取消按钮外均不可用;当有用户名输入时,记住账号也可用。
2)当密码文本框有输入时,自动登录、确定按钮皆可用,否则皆不可用。
3)勾选自动登录,那么记住账号会被同时勾选。
4)清空密码,取消自动登录选中状态并且不可用;清空账号则取消两个复选框的状态并且都不可用。
5)……

这是一个多 UI 控件交互的情景,此时很适合让 Activity 来充当一个中介者并在其中处理相关逻辑。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
public class LoginActivity extends BaseActivity {
private EditText etAccount, etPassword;
private CheckBox cbRemember, cbAuto;
private Button btnEnsure, btnCancel;
private String strAccount, strPassword; // 账户、密码
private boolean isRemember, isAuto; // 是否记住账号、自动登录
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_test);
initViews();
}
private void initViews() {
etAccount = findViewById(R.id.login_account_et);
etPassword = findViewById(R.id.login_password_et);
cbRemember = findViewById(R.id.login_remember_cb);
cbAuto = findViewById(R.id.login_auto_cb);
btnEnsure = findViewById(R.id.login_ensure_btn);
btnCancel = findViewById(R.id.login_cancel_btn);
// 账户
etAccount.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 回传数据
strAccount = s.toString();
// 通知 Activity 状态改变
change();
}
@Override
public void afterTextChanged(Editable s) {}
});
// 密码
etPassword.setEnabled(false);
etPassword.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 回传数据
strPassword = s.toString();
// 通知 Activity 状态改变
change();
}
@Override
public void afterTextChanged(Editable s) {}
});
// 记住账户
cbRemember.setEnabled(false);
cbRemember.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// 回传数据
isRemember = isChecked;
// 通知 Activity 状态改变
change();
}
});
// 自动登录
cbAuto.setEnabled(false);
cbAuto.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// 回传数据
isAuto = isChecked;
isRemember = isAuto || isRemember;
// 通知 Activity 状态改变
change();
}
});
// 确定按钮
btnEnsure.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_LONG).show();
}
});
// 取消按钮
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(LoginActivity.this, "取消登录", Toast.LENGTH_LONG).show();
}
});
}
/**
* 各控件状态改变后协调各控件状态显示
*/
private void change() {
if (TextUtils.isEmpty(strAccount)) {
etPassword.setEnabled(false);
cbRemember.setEnabled(false);
cbAuto.setEnabled(false);
btnEnsure.setEnabled(false);
btnCancel.setEnabled(true);
isRemember = false;
isAuto = false;
} else if (!TextUtils.isEmpty(strAccount)
&& TextUtils.isEmpty(strPassword)) {
etPassword.setEnabled(true);
cbRemember.setEnabled(true);
cbAuto.setEnabled(false);
btnEnsure.setEnabled(false);
btnCancel.setEnabled(true);
isAuto = false;
} else if (!TextUtils.isEmpty(strAccount)
&& !TextUtils.isEmpty(strPassword)) {
etPassword.setEnabled(true);
cbRemember.setEnabled(true);
cbAuto.setEnabled(true);
btnEnsure.setEnabled(true);
btnCancel.setEnabled(true);
}
cbRemember.setChecked(isRemember);
cbAuto.setChecked(isAuto);
}
}